/*
Copyright (C) 1997-2001 Id Software, Inc.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/

// Created on 16.11.2005 by RST.
// $Id: GameCombat.java,v 1.3 2006/01/21 21:53:32 salomo Exp $
using System;
using Defines = Suake2.UI.Defines;
using Globals = Suake2.UI.Globals;
using Com = Suake2.UI.qcommon.Com;
using Math3D = Suake2.UI.util.Math3D;
namespace Suake2.UI.game
{
	
	public class GameCombat
	{
		
		/// <summary> CanDamage
		/// 
		/// Returns true if the inflictor can directly damage the target. Used for
		/// explosions and melee attacks.
		/// </summary>
		internal static bool CanDamage(edict_t targ, edict_t inflictor)
		{
			float[] dest = new float[]{0, 0, 0};
			trace_t trace;
			
			// bmodels need special checking because their origin is 0,0,0
			if (targ.movetype == Defines.MOVETYPE_PUSH)
			{
				Math3D.VectorAdd(targ.absmin, targ.absmax, dest);
				Math3D.VectorScale(dest, 0.5f, dest);
				trace = GameBase.gi.trace(inflictor.s.origin, Globals.vec3_origin, Globals.vec3_origin, dest, inflictor, Defines.MASK_SOLID);
				if (trace.fraction == 1.0f)
					return true;
				if (trace.ent == targ)
					return true;
				return false;
			}
			
			trace = GameBase.gi.trace(inflictor.s.origin, Globals.vec3_origin, Globals.vec3_origin, targ.s.origin, inflictor, Defines.MASK_SOLID);
			if (trace.fraction == 1.0)
				return true;
			
			Math3D.VectorCopy(targ.s.origin, dest);
			dest[0] = (float) (dest[0] + 15.0);
			dest[1] = (float) (dest[1] + 15.0);
			trace = GameBase.gi.trace(inflictor.s.origin, Globals.vec3_origin, Globals.vec3_origin, dest, inflictor, Defines.MASK_SOLID);
			if (trace.fraction == 1.0)
				return true;
			
			Math3D.VectorCopy(targ.s.origin, dest);
			dest[0] = (float) (dest[0] + 15.0);
			dest[1] = (float) (dest[1] - 15.0);
			trace = GameBase.gi.trace(inflictor.s.origin, Globals.vec3_origin, Globals.vec3_origin, dest, inflictor, Defines.MASK_SOLID);
			if (trace.fraction == 1.0)
				return true;
			
			Math3D.VectorCopy(targ.s.origin, dest);
			dest[0] = (float) (dest[0] - 15.0);
			dest[1] = (float) (dest[1] + 15.0);
			trace = GameBase.gi.trace(inflictor.s.origin, Globals.vec3_origin, Globals.vec3_origin, dest, inflictor, Defines.MASK_SOLID);
			if (trace.fraction == 1.0)
				return true;
			
			Math3D.VectorCopy(targ.s.origin, dest);
			dest[0] = (float) (dest[0] - 15.0);
			dest[1] = (float) (dest[1] - 15.0);
			trace = GameBase.gi.trace(inflictor.s.origin, Globals.vec3_origin, Globals.vec3_origin, dest, inflictor, Defines.MASK_SOLID);
			if (trace.fraction == 1.0)
				return true;
			
			return false;
		}
		
		/// <summary> Killed.</summary>
		public static void  Killed(edict_t targ, edict_t inflictor, edict_t attacker, int damage, float[] point)
		{
			Com.DPrintf("Killing a " + targ.classname + "\n");
			if (targ.health < - 999)
				targ.health = - 999;
			
			targ.enemy = attacker;
			
			if ((targ.svflags & Defines.SVF_MONSTER) != 0 && (targ.deadflag != Defines.DEAD_DEAD))
			{
				//			targ.svflags |= SVF_DEADMONSTER; // now treat as a different
				// content type
				if (0 == (targ.monsterinfo.aiflags & Defines.AI_GOOD_GUY))
				{
					GameBase.level.killed_monsters++;
					if (GameBase.coop.value_Renamed != 0 && attacker.client != null)
						attacker.client.resp.score++;
					// medics won't heal monsters that they kill themselves
					if (attacker.classname.Equals("monster_medic"))
						targ.owner = attacker;
				}
			}
			
			if (targ.movetype == Defines.MOVETYPE_PUSH || targ.movetype == Defines.MOVETYPE_STOP || targ.movetype == Defines.MOVETYPE_NONE)
			{
				// doors, triggers,
				// etc
				targ.die.die(targ, inflictor, attacker, damage, point);
				return ;
			}
			
			if ((targ.svflags & Defines.SVF_MONSTER) != 0 && (targ.deadflag != Defines.DEAD_DEAD))
			{
				targ.touch = null;
				Monster.monster_death_use(targ);
			}
			
			targ.die.die(targ, inflictor, attacker, damage, point);
		}
		
		/// <summary> SpawnDamage.</summary>
		internal static void  SpawnDamage(int type, float[] origin, float[] normal, int damage)
		{
			if (damage > 255)
				damage = 255;
			GameBase.gi.WriteByte(Defines.svc_temp_entity);
			GameBase.gi.WriteByte(type);
			//		gi.WriteByte (damage);
			GameBase.gi.WritePosition(origin);
			GameBase.gi.WriteDir(normal);
			GameBase.gi.multicast(origin, Defines.MULTICAST_PVS);
		}
		
		internal static int CheckPowerArmor(edict_t ent, float[] point, float[] normal, int damage, int dflags)
		{
			gclient_t client;
			int save;
			int power_armor_type;
			int index = 0;
			int damagePerCell;
			int pa_te_type;
			int power = 0;
			int power_used;
			
			if (damage == 0)
				return 0;
			
			client = ent.client;
			
			if ((dflags & Defines.DAMAGE_NO_ARMOR) != 0)
				return 0;
			
			if (client != null)
			{
				power_armor_type = GameItems.PowerArmorType(ent);
				if (power_armor_type != Defines.POWER_ARMOR_NONE)
				{
					index = GameItems.ITEM_INDEX(GameItems.FindItem("Cells"));
					power = client.pers.inventory[index];
				}
			}
			else if ((ent.svflags & Defines.SVF_MONSTER) != 0)
			{
				power_armor_type = ent.monsterinfo.power_armor_type;
				power = ent.monsterinfo.power_armor_power;
			}
			else
				return 0;
			
			if (power_armor_type == Defines.POWER_ARMOR_NONE)
				return 0;
			if (power == 0)
				return 0;
			
			if (power_armor_type == Defines.POWER_ARMOR_SCREEN)
			{
				float[] vec = new float[]{0, 0, 0};
				float dot;
				float[] forward = new float[]{0, 0, 0};
				
				// only works if damage point is in front
				Math3D.AngleVectors(ent.s.angles, forward, null, null);
				Math3D.VectorSubtract(point, ent.s.origin, vec);
				Math3D.VectorNormalize(vec);
				dot = Math3D.DotProduct(vec, forward);
				if (dot <= 0.3)
					return 0;
				
				damagePerCell = 1;
				pa_te_type = Defines.TE_SCREEN_SPARKS;
				damage = damage / 3;
			}
			else
			{
				damagePerCell = 2;
				pa_te_type = Defines.TE_SHIELD_SPARKS;
				damage = (2 * damage) / 3;
			}
			
			save = power * damagePerCell;
			
			if (save == 0)
				return 0;
			if (save > damage)
				save = damage;
			
			SpawnDamage(pa_te_type, point, normal, save);
			ent.powerarmor_time = GameBase.level.time + 0.2f;
			
			power_used = save / damagePerCell;
			
			if (client != null)
				client.pers.inventory[index] -= power_used;
			else
				ent.monsterinfo.power_armor_power -= power_used;
			return save;
		}
		
		internal static int CheckArmor(edict_t ent, float[] point, float[] normal, int damage, int te_sparks, int dflags)
		{
			gclient_t client;
			int save;
			int index;
			gitem_t armor;
			
			if (damage == 0)
				return 0;
			
			client = ent.client;
			
			if (client == null)
				return 0;
			
			if ((dflags & Defines.DAMAGE_NO_ARMOR) != 0)
				return 0;
			
			index = GameItems.ArmorIndex(ent);
			
			if (index == 0)
				return 0;
			
			armor = GameItems.GetItemByIndex(index);
			gitem_armor_t garmor = (gitem_armor_t) armor.info;
			
			if (0 != (dflags & Defines.DAMAGE_ENERGY))
			{
				//UPGRADE_WARNING: Data types in Visual C# might be different.  Verify the accuracy of narrowing conversions. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1042'"
				save = (int) System.Math.Ceiling(garmor.energy_protection * damage);
			}
			else
			{
				//UPGRADE_WARNING: Data types in Visual C# might be different.  Verify the accuracy of narrowing conversions. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1042'"
				save = (int) System.Math.Ceiling(garmor.normal_protection * damage);
			}
			
			if (save >= client.pers.inventory[index])
				save = client.pers.inventory[index];
			
			if (save == 0)
				return 0;
			
			client.pers.inventory[index] -= save;
			SpawnDamage(te_sparks, point, normal, save);
			
			return save;
		}
		
		public static void  M_ReactToDamage(edict_t targ, edict_t attacker)
		{
			if ((null != attacker.client) && 0 != (attacker.svflags & Defines.SVF_MONSTER))
				return ;
			
			if (attacker == targ || attacker == targ.enemy)
				return ;
			
			// if we are a good guy monster and our attacker is a player
			// or another good guy, do not get mad at them
			if (0 != (targ.monsterinfo.aiflags & Defines.AI_GOOD_GUY))
			{
				if (attacker.client != null || (attacker.monsterinfo.aiflags & Defines.AI_GOOD_GUY) != 0)
					return ;
			}
			
			// we now know that we are not both good guys
			
			// if attacker is a client, get mad at them because he's good and we're
			// not
			if (attacker.client != null)
			{
				targ.monsterinfo.aiflags &= ~ Defines.AI_SOUND_TARGET;
				
				// this can only happen in coop (both new and old enemies are
				// clients)
				// only switch if can't see the current enemy
				if (targ.enemy != null && targ.enemy.client != null)
				{
					if (GameUtil.visible(targ, targ.enemy))
					{
						targ.oldenemy = attacker;
						return ;
					}
					targ.oldenemy = targ.enemy;
				}
				targ.enemy = attacker;
				if (0 == (targ.monsterinfo.aiflags & Defines.AI_DUCKED))
					GameUtil.FoundTarget(targ);
				return ;
			}
			
			// it's the same base (walk/swim/fly) type and a different classname and
			// it's not a tank
			// (they spray too much), get mad at them
			if (((targ.flags & (Defines.FL_FLY | Defines.FL_SWIM)) == (attacker.flags & (Defines.FL_FLY | Defines.FL_SWIM))) && (!(targ.classname.Equals(attacker.classname))) && (!(attacker.classname.Equals("monster_tank"))) && (!(attacker.classname.Equals("monster_supertank"))) && (!(attacker.classname.Equals("monster_makron"))) && (!(attacker.classname.Equals("monster_jorg"))))
			{
				if (targ.enemy != null && targ.enemy.client != null)
					targ.oldenemy = targ.enemy;
				targ.enemy = attacker;
				if (0 == (targ.monsterinfo.aiflags & Defines.AI_DUCKED))
					GameUtil.FoundTarget(targ);
			}
			// if they *meant* to shoot us, then shoot back
			else if (attacker.enemy == targ)
			{
				if (targ.enemy != null && targ.enemy.client != null)
					targ.oldenemy = targ.enemy;
				targ.enemy = attacker;
				if (0 == (targ.monsterinfo.aiflags & Defines.AI_DUCKED))
					GameUtil.FoundTarget(targ);
			}
			// otherwise get mad at whoever they are mad at (help our buddy) unless
			// it is us!
			else if (attacker.enemy != null && attacker.enemy != targ)
			{
				if (targ.enemy != null && targ.enemy.client != null)
					targ.oldenemy = targ.enemy;
				targ.enemy = attacker.enemy;
				if (0 == (targ.monsterinfo.aiflags & Defines.AI_DUCKED))
					GameUtil.FoundTarget(targ);
			}
		}
		
		internal static bool CheckTeamDamage(edict_t targ, edict_t attacker)
		{
			//FIXME make the next line real and uncomment this block
			// if ((ability to damage a teammate == OFF) && (targ's team ==
			// attacker's team))
			return false;
		}
		
		/// <summary> T_RadiusDamage.</summary>
		internal static void  T_RadiusDamage(edict_t inflictor, edict_t attacker, float damage, edict_t ignore, float radius, int mod)
		{
			float points;
			EdictIterator edictit = null;
			
			float[] v = new float[]{0, 0, 0};
			float[] dir = new float[]{0, 0, 0};
			
			while ((edictit = GameBase.findradius(edictit, inflictor.s.origin, radius)) != null)
			{
				edict_t ent = edictit.o;
				if (ent == ignore)
					continue;
				if (ent.takedamage == 0)
					continue;
				
				Math3D.VectorAdd(ent.mins, ent.maxs, v);
				Math3D.VectorMA(ent.s.origin, 0.5f, v, v);
				Math3D.VectorSubtract(inflictor.s.origin, v, v);
				points = damage - 0.5f * Math3D.VectorLength(v);
				if (ent == attacker)
					points = points * 0.5f;
				if (points > 0)
				{
					if (CanDamage(ent, inflictor))
					{
						Math3D.VectorSubtract(ent.s.origin, inflictor.s.origin, dir);
						//UPGRADE_WARNING: Data types in Visual C# might be different.  Verify the accuracy of narrowing conversions. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1042'"
						T_Damage(ent, inflictor, attacker, dir, inflictor.s.origin, Globals.vec3_origin, (int) points, (int) points, Defines.DAMAGE_RADIUS, mod);
					}
				}
			}
		}
		
		public static void  T_Damage(edict_t targ, edict_t inflictor, edict_t attacker, float[] dir, float[] point, float[] normal, int damage, int knockback, int dflags, int mod)
		{
			gclient_t client;
			int take;
			int save;
			int asave;
			int psave;
			int te_sparks;
			
			if (targ.takedamage == 0)
				return ;
			
			// friendly fire avoidance
			// if enabled you can't hurt teammates (but you can hurt yourself)
			// knockback still occurs
			//UPGRADE_WARNING: Data types in Visual C# might be different.  Verify the accuracy of narrowing conversions. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1042'"
			if ((targ != attacker) && ((GameBase.deathmatch.value_Renamed != 0 && 0 != ((int) (GameBase.dmflags.value_Renamed) & (Defines.DF_MODELTEAMS | Defines.DF_SKINTEAMS))) || GameBase.coop.value_Renamed != 0))
			{
				if (GameUtil.OnSameTeam(targ, attacker))
				{
					//UPGRADE_WARNING: Data types in Visual C# might be different.  Verify the accuracy of narrowing conversions. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1042'"
					if (((int) (GameBase.dmflags.value_Renamed) & Defines.DF_NO_FRIENDLY_FIRE) != 0)
						damage = 0;
					else
						mod |= Defines.MOD_FRIENDLY_FIRE;
				}
			}
			GameBase.meansOfDeath = mod;
			
			// easy mode takes half damage
			if (GameBase.skill.value_Renamed == 0 && GameBase.deathmatch.value_Renamed == 0 && targ.client != null)
			{
				damage = (int) (damage * 0.5);
				if (damage == 0)
					damage = 1;
			}
			
			client = targ.client;
			
			if ((dflags & Defines.DAMAGE_BULLET) != 0)
				te_sparks = Defines.TE_BULLET_SPARKS;
			else
				te_sparks = Defines.TE_SPARKS;
			
			Math3D.VectorNormalize(dir);
			
			// bonus damage for suprising a monster
			if (0 == (dflags & Defines.DAMAGE_RADIUS) && (targ.svflags & Defines.SVF_MONSTER) != 0 && (attacker.client != null) && (targ.enemy == null) && (targ.health > 0))
				damage *= 2;
			
			if ((targ.flags & Defines.FL_NO_KNOCKBACK) != 0)
				knockback = 0;
			
			// figure momentum add
			if (0 == (dflags & Defines.DAMAGE_NO_KNOCKBACK))
			{
				if ((knockback != 0) && (targ.movetype != Defines.MOVETYPE_NONE) && (targ.movetype != Defines.MOVETYPE_BOUNCE) && (targ.movetype != Defines.MOVETYPE_PUSH) && (targ.movetype != Defines.MOVETYPE_STOP))
				{
					float[] kvel = new float[]{0, 0, 0};
					float mass;
					
					if (targ.mass < 50)
						mass = 50;
					else
						mass = targ.mass;
					
					if (targ.client != null && attacker == targ)
					{
						//UPGRADE_WARNING: Data types in Visual C# might be different.  Verify the accuracy of narrowing conversions. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1042'"
						Math3D.VectorScale(dir, 1600.0f * (float) knockback / mass, kvel);
					}
					// the rocket jump hack...
					else
					{
						//UPGRADE_WARNING: Data types in Visual C# might be different.  Verify the accuracy of narrowing conversions. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1042'"
						Math3D.VectorScale(dir, 500.0f * (float) knockback / mass, kvel);
					}
					
					Math3D.VectorAdd(targ.velocity, kvel, targ.velocity);
				}
			}
			
			take = damage;
			save = 0;
			
			// check for godmode
			if ((targ.flags & Defines.FL_GODMODE) != 0 && 0 == (dflags & Defines.DAMAGE_NO_PROTECTION))
			{
				take = 0;
				save = damage;
				SpawnDamage(te_sparks, point, normal, save);
			}
			
			// check for invincibility
			if ((client != null && client.invincible_framenum > GameBase.level.framenum) && 0 == (dflags & Defines.DAMAGE_NO_PROTECTION))
			{
				if (targ.pain_debounce_time < GameBase.level.time)
				{
					GameBase.gi.sound(targ, Defines.CHAN_ITEM, GameBase.gi.soundindex("items/protect4.wav"), 1, Defines.ATTN_NORM, 0);
					targ.pain_debounce_time = GameBase.level.time + 2;
				}
				take = 0;
				save = damage;
			}
			
			psave = CheckPowerArmor(targ, point, normal, take, dflags);
			take -= psave;
			
			asave = CheckArmor(targ, point, normal, take, te_sparks, dflags);
			take -= asave;
			
			// treat cheat/powerup savings the same as armor
			asave += save;
			
			// team damage avoidance
			if (0 == (dflags & Defines.DAMAGE_NO_PROTECTION) && CheckTeamDamage(targ, attacker))
				return ;
			
			// do the damage
			if (take != 0)
			{
				if (0 != (targ.svflags & Defines.SVF_MONSTER) || (client != null))
					SpawnDamage(Defines.TE_BLOOD, point, normal, take);
				else
					SpawnDamage(te_sparks, point, normal, take);
				
				targ.health = targ.health - take;
				
				if (targ.health <= 0)
				{
					if ((targ.svflags & Defines.SVF_MONSTER) != 0 || (client != null))
						targ.flags |= Defines.FL_NO_KNOCKBACK;
					Killed(targ, inflictor, attacker, take, point);
					return ;
				}
			}
			
			if ((targ.svflags & Defines.SVF_MONSTER) != 0)
			{
				M_ReactToDamage(targ, attacker);
				if (0 == (targ.monsterinfo.aiflags & Defines.AI_DUCKED) && (take != 0))
				{
					targ.pain.pain(targ, attacker, knockback, take);
					// nightmare mode monsters don't go into pain frames often
					if (GameBase.skill.value_Renamed == 3)
						targ.pain_debounce_time = GameBase.level.time + 5;
				}
			}
			else if (client != null)
			{
				if (((targ.flags & Defines.FL_GODMODE) == 0) && (take != 0))
					targ.pain.pain(targ, attacker, knockback, take);
			}
			else if (take != 0)
			{
				if (targ.pain != null)
					targ.pain.pain(targ, attacker, knockback, take);
			}
			
			// add to the damage inflicted on a player this frame
			// the total will be turned into screen blends and view angle kicks
			// at the end of the frame
			if (client != null)
			{
				client.damage_parmor += psave;
				client.damage_armor += asave;
				client.damage_blood += take;
				client.damage_knockback += knockback;
				Math3D.VectorCopy(point, client.damage_from);
			}
		}
	}
}